import {
  debounceTime,
  distinctUntilChanged,
  filter,
  map,
  Observable,
  Subject,
  switchMap,
} from "rxjs";

export type OptionData = { label: any; value: any }[];
export type ConvertFunc<R> = (opData: OptionData) => R[];
export type UpdateFunc = (text: string) => void;

interface BasicOptions {
  debounceTime?: number;
  labelField?: string;
  valueField?: string;
}

export interface FuzzSearchOption<R> extends BasicOptions {
  convertFunc: ConvertFunc<R>;
}

export interface FuzzSearchOption2 extends BasicOptions {
  convertFunc: undefined;
}

interface Response {
  data?: any;
}

export function createFuzzSearchObserv<T extends Response, R>(
  fetchObsv: (t: string, ...args: any[]) => Observable<T>,
  otherParams: any[],
  options: FuzzSearchOption<R>
): [UpdateFunc, Observable<R[]>];
export function createFuzzSearchObserv<T extends Response, R>(
  fetchObsv: (t: string, ...args: any[]) => Observable<T>,
  otherParams: any[],
  options: FuzzSearchOption2
): [UpdateFunc, Observable<OptionData>];
export function createFuzzSearchObserv<T extends Response, R>(
  fetchObsv: (t: string, ...args: any[]) => Observable<T>,
  otherParams: any[],
  options: FuzzSearchOption<R> | FuzzSearchOption2
): [UpdateFunc, Observable<OptionData | R[]>] {
  const subject = new Subject<string>();
  const textChange = (t: string) => subject.next(t);
  const debounceTime_opt = options.debounceTime ?? 500;
  const resObsv = subject.asObservable().pipe(
    filter((t) => !!t),
    debounceTime(debounceTime_opt),
    distinctUntilChanged(),
    switchMap((t) => fetchObsv(t, ...otherParams)),
    map((resp) => {
      let defaultRet: OptionData = [];
      if (resp.data && Array.isArray(resp.data)) {
        if (!resp.data.length) {
          return defaultRet;
        }
        const defaultField = Object.keys(resp.data[0])[0];
        if (!defaultField) {
          return defaultRet;
        }
        let labelField = options.labelField ?? defaultField;
        let valueField = options.valueField ?? labelField;
        defaultRet = resp.data.map((d) => ({
          label: d[labelField],
          value: d[valueField],
        }));
        if (options.convertFunc) {
          let convertRet: R[] = [];
          convertRet = options.convertFunc(defaultRet);
          return convertRet;
        } else {
          return defaultRet;
        }
      } else {
        return defaultRet;
      }
    })
  );
  return [textChange, resObsv];
}
